package io.github.uicdb.badskill;

import io.github.uicdb.badskill.network.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.nbt.StringTag;
import net.minecraft.nbt.Tag;
import net.minecraft.network.chat.Component;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.player.Player;
import net.minecraftforge.common.capabilities.AutoRegisterCapability;
import net.minecraftforge.common.util.INBTSerializable;
import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.network.PacketDistributor;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RequiredArgsConstructor
@AutoRegisterCapability
@Mod.EventBusSubscriber(modid = BadSkill.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public class PlayerSkillCapability implements INBTSerializable<CompoundTag>, ICapabilityCoppier {
    @Getter
    public final Player player;
    public final Map<ResourceLocation, SkillOption> options = new HashMap<>();
    public final List<SkillOption> disableList = new ArrayList<>();
    public int disableTicks = 0;

    public void addAttribute(Attribute attribute, AttributeModifier modifier) {
        if (player.getAttributes().hasAttribute(attribute)) {
            var ai = player.getAttributes().getInstance(attribute);
            if (ai != null) {
                ai.removeModifier(modifier.getId());
                ai.addTransientModifier(modifier);
            }
        }
    }

    public void removeAttribute(Attribute attribute, AttributeModifier modifier) {
        if (player.getAttributes().hasAttribute(attribute)) {
            var ai = player.getAttributes().getInstance(attribute);
            if (ai != null) {
                ai.removeModifier(modifier.getId());
            }
        }
    }

    public State checkState(SkillPage.Location location, SkillOption option) {
        if (disableList.contains(option)) return State.DISABLE;
        if (options.containsKey(option.id)) return State.LEARNED;
        if (checkLearn(location,option)) {
            return State.LEARNABLE;
        }else if(option.learn(player,true)){
            return State.PARENT_NOT_ALL;
        }
        return State.PARENT_ALL;
    }

    public boolean learn(ResourceLocation page, ResourceLocation id, boolean sim) {
        SkillOption option = SkillOptionManager.INSTANCE.OPTIONS.get(id);
        if (option == null) return false;
        return learn(page, option, sim);
    }

    public void forceLearn(SkillOption option) {
        if (player instanceof ServerPlayer serverPlayer) {
            if (options.containsKey(option.id)) return;
            option.learn(player, false);
            options.put(option.id, option);
            NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> serverPlayer), new LearnResultPackage(option.id));
        }
    }

    public boolean learn(ResourceLocation page, SkillOption option, boolean sim) {
        ResourceLocation id = option.id;
        SkillPage.Location location = SkillPageManager.INSTANCE.safeGet(page).locations.get(id);
        if (location == null) return false;
        if (sim)
            return checkLearn(location, option);
        else {
            if (player instanceof ServerPlayer) {
                if (options.containsKey(option.id)) return false;
                if (checkLearn(location, option)) {
                    forceLearn(option);
                    return true;
                }
                return false;
            } else {
                boolean res = checkLearn(location, option);
                if (res) NetWorking.simpleChannel.sendToServer(new LearnCheckPackage(page, option.id));
                return res;
            }
        }
    }


    public boolean checkLearn(SkillPage.Location location, SkillOption option) {
        int result = 0;
        for (ResourceLocation parent : location.parents) {
            if (options.containsKey(parent)) result++;
        }
        return result == location.parents.size() && !options.containsKey(option.id) && option.learn(player, true);
    }

    public void retreated(SkillOption option, boolean backRes) {
        if (player instanceof ServerPlayer serverPlayer) {
            if (options.containsKey(option.id)) {
                options.remove(option.id);
                NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> serverPlayer), new RetreatedResultPackage(option.id));
            }
        }
        option.retreated(player, backRes);
    }

    public boolean isLearned(ResourceLocation option) {
        return options.containsKey(option);
    }

    public boolean isLearned(SkillOption option) {
        return options.containsKey(option.id);
    }

    public void disableAll(int ticks) {
        if (player.level().isClientSide) return;
        disableTicks = ticks;
        boolean changed = false;
        for (SkillOption option : options.values()) {
            if (!disableList.contains(option)) {
                disableList.add(option);
                option.disable(player);
                changed = true;
            }
        }
        if (changed && player instanceof ServerPlayer serverPlayer) {
            NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> serverPlayer), DisableSkillPackage.create(this));
        }
    }

    public void enableAll() {
        if (player.level().isClientSide) return;
        boolean changed = disableList.size() > 0;
        if (changed) {
            disableList.forEach(option -> option.enable(player));
            disableList.clear();
            disableTicks = 0;
            if (player instanceof ServerPlayer serverPlayer) {
                NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> serverPlayer), new EnableSkillPackage());
            }
        }
    }

    @Override
    public CompoundTag serializeNBT() {
        CompoundTag nbt = new CompoundTag();
        ListTag disTag = new ListTag();
        disableList.forEach(s -> disTag.add(StringTag.valueOf(s.id.toString())));
        nbt.put("disables", disTag);
        ListTag tag = new ListTag();
        options.keySet()
                .forEach(resourceLocation -> tag.add(StringTag.valueOf(resourceLocation.toString())));
        nbt.put("learned", tag);
        nbt.putInt("disableTicks", disableTicks);
        return nbt;
    }

    @Override
    public void deserializeNBT(CompoundTag nbt) {
        disableList.clear();
        disableTicks = nbt.getInt("disableTicks");
        nbt.getList("disables", Tag.TAG_STRING)
                .forEach(tag -> {
                    ResourceLocation key = new ResourceLocation(tag.getAsString());
                    if (options.containsKey(key)) {
                        SkillOption s = SkillOptionManager.INSTANCE.OPTIONS.get(key);
                        if (s != null)
                            disableList.add(s);
                    }
                });
        options.clear();
        nbt.getList("learned", Tag.TAG_STRING).forEach(tag -> {
            ResourceLocation location = new ResourceLocation(tag.getAsString());
            if (SkillOptionManager.INSTANCE.OPTIONS.containsKey(location)) {
                SkillOption option = SkillOptionManager.INSTANCE.OPTIONS.get(location);
                if (option != null) {
                    options.put(location, option);
                    if (!disableList.contains(option))
                        option.enable(player);
                }
            }
        });

    }

    public void tick() {
        if (disableTicks > 0) disableTicks--;
        if (disableTicks == 0) enableAll();
        options.values().stream().filter(option -> !disableList.contains(option)).forEach(o -> o.tick(player));
    }

    @SubscribeEvent
    public static void onPlayerTick(TickEvent.PlayerTickEvent event) {
        event.player.getCapability(PlayerSkillCapabilityProvider.CAPABILITY)
                .ifPresent(PlayerSkillCapability::tick);
    }

    @Override
    public void beforeCopyCapability() {

    }

    @Override
    public void afterCopyCapability() {
        if (player instanceof ServerPlayer serverPlayer) {
            NetWorking.simpleChannel.send(PacketDistributor.PLAYER.with(() -> serverPlayer),new PlayerLearnedSyncPackage(options.keySet()));
        }
    }

    public enum State {
        LEARNED(Component.translatable("tip.badskill.state.learned")),
        LEARNABLE(Component.translatable("tip.badskill.state.learnable")),
        PARENT_ALL(Component.translatable("tip.badskill.state.parent_all")),
        PARENT_NOT_ALL(Component.translatable("tip.badskill.state.parent_not_all")),
        DISABLE(Component.translatable("tip.badskill.disable"));
        @Getter
        private final Component tip;

        State(Component tip) {
            this.tip = tip;
        }

    }
}
